/* eslint-disable */

export class Enum {
  value: string;

  constructor(value: string) {
    this.value = value;
  }

  get __type__() {
    return {};
  }

  static makeEnum(name: string, _enum: string[]) {
    let result = class extends Enum {
      constructor(value: string) {
        super(value);
      }
      get __type__(): any {
        return result;
      }
    };
    result['__name__'] = name;
    result['__enum__'] = _enum;
    return result;
  }
}

export class Array {
  items: any;
  constructor(items: any) {
    this.items = items;
  }
}

export class MetaObject {
  get __type__() {
    return {};
  }

  constructor() {
    let t: any = this.__type__;
    console.log('MetaObject', t);
    t.__properties__.forEach((v: any, k: string) => {
      switch (v) {
        case Enum:
        case String:
          this[k] = '';
          break;
        case Number:
          this[k] = 0;
          break;
        case Boolean:
          this[k] = false;
          break;
        case Array:
          this[k] = [];
          break;
        case Object:
          this[k] = {};
          break;
        default:
          this[k] = null;
      }
    });
  }

  static makeType(name: string) {
    let result = class extends MetaObject {
      get __type__(): any {
        return result;
      }
    };
    result['__name__'] = name;
    result['__fields__'] = new Map();
    result['__fields_value__'] = new Map();
    result['__properties__'] = new Map();
    return result;
  }

  static makeArray() {
    const result = class extends Array {
      get type() {
        return result;
      }

      constructor(items: any) {
        super(items);
      }
    };

    return result;
  }
}

export class MetadataProvider {
  types = new Map();
  fields = new Map();

  construct() {}

  get_type(id: string | number) {
    return this.types[id];
  }

  _add_type(typeinfo: { name: string | number }) {
    this.types[typeinfo.name] = typeinfo;
  }

  _make_type(id: string) {
    let result = this.get_type(id);
    if (!result) {
      result = MetaObject.makeType(id);
      this.types[id] = result;
    }
    return result;
  }

  get_fields(id: string | number) {
    return this.fields[id];
  }

  _add_fields(fieldsInfo: { name: string | number }) {
    this.types[fieldsInfo.name] = fieldsInfo;
  }

  _make_array(id: string) {
    let result = this.get_type(`${id}`);
    if (!result) {
      this._make_type(id);
      result = Array; //array(str('u'), id)
    }
    return result;
  }

  _make_enum(id: string, _enum: string[]) {
    let result = this.get_type(id);
    if (!result) {
      result = Enum.makeEnum(id, _enum);
    }
    return result;
  }

  add_types(typedef: any[]) {
    throw new Error('not implemented error');
  }

  get_objects(objectdef: { [x: string]: any }) {
    throw new Error('not implemented error');
  }
}

export class JsonMetadataProvider extends MetadataProvider {
  constructor() {
    super();
    this.types['str'] = String;
    this.types['int'] = Number;
    this.types['double'] = Number;
    this.types['bool'] = Boolean;
    this.types['object'] = Object;
  }

  __visit_type(id: string, value: { [x: string]: any; properties: never[]; value: never[] }) {
    let valueType = value['type'];

    if (valueType === 'object') {
      let typeInfo = this._make_type(id);
      const properties = value.properties || [];
      properties.forEach((p) => {
        const propertyName = `${p['id']}`;
        const property = this.__visit_property(typeInfo, propertyName, p);
        typeInfo.__properties__.set(propertyName, property);
      });

      let fields = value.fields || [];
      fields.forEach((f: any) => {
        const filedName = `${f.id}`;
        const field = this.__visit_field(`${f.id}`, f);
        typeInfo.__fields__.set(filedName, field);
      });

      let fieldsValue = value.value || {};
      for (let fv in fieldsValue) {
        typeInfo.__fields_value__.set(fv, fieldsValue[fv]);
      }

      return typeInfo;
    } else if (valueType === 'array') {
      let item = value['items'];
      let arrayType: any = this.__visit_type(item['type'], item);
      return this._make_array(arrayType.__name__);
    } else if (valueType === 'ref') {
      const refid = value['refId'] || 'object';
      return this._make_type(`${refid}`);
    } else if (valueType === 'number') {
      let format = value['format'] || 'double';
      return this.get_type(format);
    } else if (valueType === 'text') {
      return this.get_type('str');
    } else if (valueType == 'enum') {
      let _enum: string[] = [];
      value['enum'].forEach((en: any) => {
        _enum.push(en['value']);
      });
      return this._make_enum(value.id, _enum);
    } else if (valueType == 'bool') {
      return this.get_type('bool');
    } else {
      throw new Error('invalid type value');
    }
  }

  __visit_property(type: { __name__: any }, id: string, value: never) {
    let valuetype: any = value['type'];
    if (valuetype === 'object') {
      valuetype = this.__visit_type(`${type.__name__}_${id}`, value);
      return valuetype;
    } else {
      return this.__visit_type(valuetype, value);
    }
  }

  __visit_field(id: string, value: any) {
    return this.__visit_type(id, value);
  }

  add_types(typedef: any[]) {
    typedef.forEach((item) => {
      const typeInfo = this.__visit_type(`${item['id']}`, item);
      this.types[typeInfo.__name__] = typeInfo;
    });
  }

  get_objects(objectdef: { [x: string]: any }) {
    let refid = objectdef['_refId'];
    let typename = `${refid}`;
    let typeinfo = this.get_type(typename);
    return this.__visit_object(typeinfo, objectdef);
  }

  __visit_object(typeinfo: { new (): any; __properties__: any[] }, value: { [x: string]: any }) {
    let result = new typeinfo();
    typeinfo.__properties__.forEach((type, name) => {
      let property = value[name];
      if (property) {
        if (type.prototype instanceof MetaObject) {
          result[name] = this.__visit_object(type, property);
        }
      }
      result[name] = this.__visit_value(type, property);
    });
    return result;
  }

  __visit_value(valuetype: { new (arg0: any): any; prototype: any }, value: any) {
    if (valuetype instanceof Array) {
      return value;
    }
    if (valuetype.prototype instanceof Enum) {
      return new valuetype(value);
    } else {
      return value;
    }
  }
}
export default new JsonMetadataProvider();
